//-----Irradiance Cache---------------------------------------------------


#ifndef __IRRADIANCECACHE__
#define __IRRADIANCECACHE__

#include "Octree.h"
#include "Scene.h"
#include "RGBColor.h"
#include "BVH.h"
#include "Ray.h"
#include "Sampler.h"
#include "Jittered.h"
#include "Tracer.h"
#include "ShadeRec.h"
#include "Common.h"
#include <memory>
#include <string>
#include <iostream>
#include "Logger.h"
#include "MatlabUtilityDebuger.h"
using std::string;

namespace Engine
{

	/**
	*	Irradiance Cache
	*
	*	Author-> Adrian De Barro
	*/

	#define MAXH_DEPTH 1
	//#define DEBUG_MODE
	#define NUMBER_SAMPLES 529
	#define NUMBER_SETS 83
	#define USE_ORTHAGONAL_SHIFTING true
	#define DEBUG_SAMPLES
    #define AMOUNT_DEVISABLE_CONSTANT 60;

	class IrradianceCache
	{
	public:
		IrradianceCache() : m_currentShadeRec(ShadeRec())
		{
			m_currentShadeRec = ShadeRec();
		}
		//for testing purposes only
		IrradianceCache(Scene* p_scene, Tracer* p_tracerPtr) : m_currentShadeRec(ShadeRec())
		{
			octreeStructure = new Octree(p_scene);
			accelerationStructure = new BVH(p_scene->m_sceneObjects, MIDDLE_POINT, true);
			m_scenePtr = p_scene;
			m_sampler = new Jittered(NUMBER_SAMPLES, NUMBER_SETS);
			m_sampler->GenerateSamples();
			m_sampler->MapSamplesToUnitDisk();
			m_sampler->MapSamplesToHemisphere(0, true);
			m_tracerPtr = p_tracerPtr;
			m_currentShadeRec = ShadeRec();
			GetMinmumRayLength();
		}

		IrradianceCache(Scene* p_scene, Engine::BVH* p_bvh, Tracer* p_tracerPtr) : m_currentShadeRec(ShadeRec())
		{
			octreeStructure = new Octree(p_bvh);
			accelerationStructure = p_bvh;
			m_scenePtr = p_scene;
			m_sampler = new Jittered(NUMBER_SAMPLES, NUMBER_SETS);
			m_sampler->GenerateSamples();
			m_tracerPtr = p_tracerPtr;
			m_currentShadeRec = ShadeRec();
			GetMinmumRayLength();
		}

		RGBColor GetIndirectLightingAtPoint(ShadeRec& p_currentPoint)
		{
			currentMaterial = p_currentPoint.object->GetMaterial()->GetMaterialName();
//			if (currentMaterial.compare("ceiling") == 0)
//			{
//#ifdef DEBUG_MODE
//				cout << "ceiling!!!!!!!!!!!!!!" << endl;
//#endif
//			}
//			

			p_currentQuad = (Quad*)p_currentPoint.object;
			m_currentShadeRec = ShadeRec(p_currentPoint);
			
			//}
			//else
			//{
			//	//cout << "leftwall" << endl;
			//	p_currentQuad = (Quad*)p_currentPoint.object;
			//	m_currentShadeRec = ShadeRec(p_currentPoint);
			//}
			
			Point3D normal(p_currentPoint.normal.x, p_currentPoint.normal.y, p_currentPoint.normal.z);
			RadiancePoint* newPoint = new RadiancePoint(p_currentPoint.localHitPoint, normal, p_currentPoint.ray, currentMaterial);
			//p_currentPoint.object->PrintObjectName();
			return GetIndirectLightingAtPoint(newPoint);
		}

		RGBColor GetIndirectLightingAtPoint(RadiancePoint* p_currentPoint)
		{
			return GetOrCreate(p_currentPoint);
		}

		RGBColor GetIndirectLightingAtPoint(Point3D& p_position)
		{	
			RadiancePoint* newRadiancePoint = new RadiancePoint();
			newRadiancePoint->m_position = p_position;
			return GetOrCreate(newRadiancePoint);
		}

		RGBColor GetOrCreate(RadiancePoint* p_currentPoint)
		{
			RadiancePoint *radianceExist = octreeStructure->GetRadiancePoint(p_currentPoint->m_position);
			vector<RadiancePoint*> availableRadiancePoints;
			//create new radiane point
			if (radianceExist == NULL && !octreeStructure->GetPoints(p_currentPoint, availableRadiancePoints))
			{
				
				FireSamples(p_currentPoint);
				//Logger::LogInfo("Firing samples for the following");
				//p_currentPoint->m_rayLength = 0.5;
				#ifdef DEBUG_MODE
					return RGBColor(1, 0, 0);
				#else
					return p_currentPoint->radiance;
				#endif
				
		
			}
			//interpolate 
			else if (availableRadiancePoints.size() > 0 )
			{
				//Logger::LogInfo("Interpolating for result of current");
				return ComputeInterpolation(availableRadiancePoints, p_currentPoint);
			}
			//return availbale radiance point
			else 
				return radianceExist->radiance;
				
		}

		//===============================================================

		RGBColor& ComputeInterpolation(vector<RadiancePoint*> points, RadiancePoint* p_currentRadiancePoint)
		{
			float output = 0;
			RGBColor summationIrradianceWeight = 0;
			float summationWeight = 0;

			for (int index = 0; index < points.size(); index++)
			{
				RadiancePoint* currentPoint = points[index];

				float currentWeight = currentPoint->GetWeighting(p_currentRadiancePoint);

				summationIrradianceWeight = summationIrradianceWeight + currentPoint->radiance*currentWeight;
				summationWeight += currentWeight;
			}

			return summationIrradianceWeight / summationWeight;
		}

		//==============================================================

		RGBColor FireSamples(ShadeRec& sr)
		{
			RadiancePoint* newRadiancePoint = new RadiancePoint();
			newRadiancePoint->m_position = sr.hitPoint;
			newRadiancePoint->hitPointNormal = Point3D(sr.normal.x, sr.normal.y, sr.normal.z);
			FireSamples(newRadiancePoint);
		}

		//==============================================================

		RGBColor FireSamples(RadiancePoint* p_currentPoint)
		{
			//TODO fix this
			vector<Point3D> SamplePositions;

			//get samples in hemispherival mode
			vector<ShadeRec> collisions;
			ShadeRec rec;
			int depth = 0;
			Sample(p_currentPoint, depth);

			return p_currentPoint->radiance;
		}

		//==============================================================

		void Sample(RadiancePoint* p_collisionPoint, int& currentDepth)
		{
			vector<ShadeRec*> collisions;

			/*if (++currentDepth <= MAXH_DEPTH)
			{*/
			vector<Point3D> SamplePositions;

			/*if (currentMaterial.compare("rightWall") == 0)
			{
				cout << "debugging" << endl;
			}
			else if (currentMaterial.compare("leftWall") == 0)
			{
				cout << "leftwall" << endl;
			}
			else
			{
				cout << "ceiling or floor" << endl;
			}*/

			vector<Point3D> samples;

			RGBColor resultingOutput;
			for (int sampleIndex = 0; sampleIndex < NUMBER_SAMPLES; ++sampleIndex)
			{
				try
				{
					Point3D currentSample = m_sampler->SampleHemispherical(sampleIndex, p_collisionPoint->hitPointNormal, p_collisionPoint->m_collisionRay.d);
					////questionable
					currentSample.normalize();
					samples.push_back(currentSample);
					//Ray currentRay(p_collisionPoint->GetEpsillonHitPoint(), p_collisionPoint->hitPointNormal);
					Ray currentRay(p_collisionPoint->GetEpsillonHitPoint(), currentSample);

					ShadeRec* currentCollision = new ShadeRec();

					FireSamplesAtPoint(currentRay, currentCollision);

				/*	if (currentCollision->hitAnObject)
					{
						cout << " "<< endl;
						currentCollision->object->PrintObjectName();
					}
					else
					{
						cout << " No hit" << p_collisionPoint->m_position.toString() << " DIR " << currentSample.toString() <<endl;
					}*/

					//currentCollision->object->PrintObjectName();
					collisions.push_back(currentCollision);
				}
				catch (exception e)
				{
					Logger::LogError("");
				}
			}
			
			//#define DEBUG_SAMPLES
			string output = MatlabUtilityDebuger::CreateVisualizer(samples, m_currentShadeRec);
			//#endif

			p_collisionPoint->m_objectName = output;
			p_collisionPoint->ComputeRadianceNHarmonivValue(collisions, m_minimumRayLength);
			//LOG(TRACE)<<output;
			std::for_each(collisions.begin(), collisions.end(), std::default_delete<ShadeRec>());

			//TO YSE BACK TOMORROW
			octreeStructure->InsertPoint(p_collisionPoint);
			/*}
			else
			{
				int amountSamples = m_sampler->GetNumSamples();
				
				RGBColor resultingOutput;

				for (int sampleIndex = 0; sampleIndex < amountSamples; ++sampleIndex)
				{ 
					Point3D direction = m_sampler->SampleHemispherical(sampleIndex);
					Ray currentRay(p_collisionPoint->GetEpsillonHitPoint(), direction);
					ShadeRec rec;
					Point3D intersectionPoint = accelerationStructure->TestIntersection(currentRay, rec);

					RadiancePoint * currentPoint = new RadiancePoint(intersectionPoint);
					resultingOutput += Sample(currentPoint, ++currentDepth);
				}
				p_collisionPoint->radiance = resultingOutput / NUMBER_SAMPLES;

				octreeStructure->InsertPoint(p_collisionPoint);

				return resultingOutput;
			}*/
			
		}

		//==============================================================

		void FireSamplesAtPoint(Ray& currentRay, ShadeRec* details)
		{
			double tmin = INT_MAX;
			currentRay.d.normalize();
			bool intersectionTest = accelerationStructure->TestIntersection(currentRay, *details, tmin);
			if (intersectionTest)
			{
				Engine::Material* currentMaterial = details->object->GetMaterial();
				details->myColor = currentMaterial->Shade(*details, m_scenePtr->m_sceneLight,m_tracerPtr);
			}
			else
			{
				details->myColor = RGBColor(0);
			}
		}

		//==============================================================

	private:
		Octree *octreeStructure;
		Scene* m_scenePtr;
		BVH* accelerationStructure;
		Sampler* m_sampler;
		Tracer* m_tracerPtr;
		double m_minimumRayLength;

		//for debug purposes only
		std::string currentMaterial;
		Quad *p_currentQuad;
		ShadeRec& m_currentShadeRec;

		void GetMinmumRayLength()
		{
			double rayLength = accelerationStructure->rootNode->boundingBox->GetLongestLengthAxis();
			m_minimumRayLength = rayLength / AMOUNT_DEVISABLE_CONSTANT;
		}

	protected:
	};
}

#endif // !__IRRADIANCECACHE__


//-----Irradinace Cache -------------------------------------------------